package com.example.networklab.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.networklab.common.BizException;
import com.example.networklab.common.ExceptionEnum;
import com.example.networklab.constants.MQConstants;
import com.example.networklab.domain.dto.PageDTO;
import com.example.networklab.domain.dto.TaskStatistics;
import com.example.networklab.domain.po.Task;
import com.example.networklab.domain.po.User;
import com.example.networklab.domain.query.PageQuery;
import com.example.networklab.domain.query.TeaTaskPageQuery;
import com.example.networklab.domain.req.AddOrEditTaskReq;
import com.example.networklab.domain.vo.DescCaseVO;
import com.example.networklab.domain.vo.DescVO;
import com.example.networklab.domain.vo.StuTaskVO;
import com.example.networklab.domain.vo.TeaTaskVO;
import com.example.networklab.enums.TaskStatus;
import com.example.networklab.mapper.TaskMapper;
import com.example.networklab.mq.dto.TaskStatusMQDTO;
import com.example.networklab.service.ITaskHistoryService;
import com.example.networklab.service.ITaskService;
import com.example.networklab.service.IUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 作业布置表 服务实现类
 * </p>
 *
 * @author dzw
 * @since 2025-02-27
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements ITaskService {
    private final RabbitTemplate rabbitTemplate;
    private final ITaskHistoryService taskHistoryService;
    private final IUserService userService;

    @Override
    public void addTask(AddOrEditTaskReq addTaskRequest) {
        Task task = new Task();
        BeanUtil.copyProperties(addTaskRequest, task);
        TaskStatus taskStatus = getTaskStatus(task.getStartTime(), task.getEndTime());
        task.setStatus(taskStatus);
        this.save(task);
        sendTaskStatusChangeMsg(task);
    }

    @Override
    public void editTask(Long taskId, AddOrEditTaskReq addTaskRequest) {
        // 考虑前端传过来的参数中有null的情况
        Task task = new Task();
        BeanUtil.copyProperties(addTaskRequest, task);
        task.setId(taskId);
        // 必须同时传startTime和endTime才能修改状态
        if (task.getStartTime() != null && task.getEndTime() != null) {
            // 如果当前时间在开始时间之前，可以修改开始时间和结束时间
            // 如果当前时间在开始时间之后，只可以修改结束时间
            TaskStatus taskStatus = getTaskStatus(task.getStartTime(), task.getEndTime());
            task.setStatus(taskStatus);
            sendTaskStatusChangeMsg(task);
        }
        this.updateById(task);
    }

    @Override
    public PageDTO<TeaTaskVO> teaGetTaskList(TeaTaskPageQuery teaTaskPageQuery) {
        Page<Task> page = this.lambdaQuery()
                .ge(teaTaskPageQuery.getStartTime() != null, Task::getStartTime, teaTaskPageQuery.getStartTime())
                .le(teaTaskPageQuery.getEndTime() != null, Task::getEndTime, teaTaskPageQuery.getEndTime())
                .page(teaTaskPageQuery.toMpPage("start_time", false));
        if (CollUtil.isEmpty(page.getRecords())) {
            return PageDTO.empty(page.getTotal(), page.getPages());
        }
        List<Task> tasks = page.getRecords();
        List<TaskStatistics> taskStatistics = taskHistoryService.getTaskStatistics(tasks);
        Map<Long, TaskStatistics> map = taskStatistics.stream().collect(Collectors.toMap(TaskStatistics::getTaskId, t -> t));
        ArrayList<TeaTaskVO> teaTaskVOS = new ArrayList<>(page.getRecords().size());
        for (Task task : tasks) {
            TeaTaskVO teaTaskVO = new TeaTaskVO();
            BeanUtil.copyProperties(task, teaTaskVO);
            TaskStatistics taskStatistic = map.get(task.getId());
            if (taskStatistic != null) {
                teaTaskVO.setSubmitCount(taskStatistic.getSubmitCount());
                teaTaskVO.setStudentSubmitCount(taskStatistic.getStudentSubmitCount());
                teaTaskVO.setStudentCount(taskStatistic.getStudentCount());
            }
            teaTaskVOS.add(teaTaskVO);
        }
        return PageDTO.of(page, teaTaskVOS);
    }

    @Override
    public DescCaseVO getTaskDescCase(Long taskId) {
        Task task = this.getById(taskId);
        DescCaseVO descCaseVO = new DescCaseVO();
        descCaseVO.setDesc(task.getDescription());
        descCaseVO.setTestCase(task.getTestCase());
        return descCaseVO;
    }

    @Override
    public PageDTO<StuTaskVO> stuGetTaskList(PageQuery pageQuery) {
        long id = StpUtil.getLoginIdAsLong();
        User user = userService.getById(id);
        Page<Task> page = this.lambdaQuery()
                .eq(Task::getGrade, user.getGrade())
                .eq(Task::getClassNum, user.getClassNum())
                .page(pageQuery.toMpPage("start_time", false));
        if (CollUtil.isEmpty(page.getRecords())) {
            return PageDTO.empty(page.getTotal(), page.getPages());
        }
        List<Task> tasks = page.getRecords();
        ArrayList<StuTaskVO> res = new ArrayList<>(tasks.size());
        for (Task task : tasks) {
            StuTaskVO stuTaskVO = new StuTaskVO();
            BeanUtil.copyProperties(task, stuTaskVO);
            res.add(stuTaskVO);
        }
        return PageDTO.of(page, res);
    }

    @Override
    public DescVO getTaskSpec(Long taskId) {
        Task task = this.getById(taskId);
        DescVO descVO = new DescVO();
        descVO.setDesc(task.getDescription());
        return descVO;
    }

    @Override
    public void removeTask(Long taskId) {
        Task task = this.getById(taskId);
        LocalDateTime now = LocalDateTime.now();
        if (task.getStartTime().isBefore(now)) {
            // 已经开始的作业不能删除
            throw new BizException(ExceptionEnum.DELETE_TASK_ERROR);
        }
        this.removeById(taskId);
    }

    private void sendTaskStatusChangeMsg(Task task) {
        TaskStatus taskStatus = task.getStatus();
        if (taskStatus == TaskStatus.NOT_START) {
            // 添加修改状态为IN_PROGRESS和END的定时任务
            TaskStatusMQDTO inProgress = new TaskStatusMQDTO();
            inProgress.setId(task.getId());
            inProgress.setStatus(TaskStatus.IN_PROGRESS);
            TaskStatusMQDTO end = new TaskStatusMQDTO();
            end.setId(task.getId());
            end.setStatus(TaskStatus.END);
            LocalDateTime now = LocalDateTime.now();
            Duration startDuration = Duration.between(now, task.getStartTime());
            Duration endDuration = Duration.between(now, task.getEndTime());
            sendDelayMsg(MQConstants.DELAY_EXCHANGE_NAME, MQConstants.DELAY_TASK_STATUS_KEY, inProgress, (int) startDuration.toMillis());
            sendDelayMsg(MQConstants.DELAY_EXCHANGE_NAME, MQConstants.DELAY_TASK_STATUS_KEY, end, (int) endDuration.toMillis());
        } else if (taskStatus == TaskStatus.IN_PROGRESS) {
            // 添加修改状态为END的定时任务
            TaskStatusMQDTO end = new TaskStatusMQDTO();
            end.setId(task.getId());
            end.setStatus(TaskStatus.END);
            LocalDateTime now = LocalDateTime.now();
            Duration endDuration = Duration.between(now, task.getEndTime());
            sendDelayMsg(MQConstants.DELAY_EXCHANGE_NAME, MQConstants.DELAY_TASK_STATUS_KEY, end, (int) endDuration.toMillis());
        }
    }

    private void sendDelayMsg(String exchange, String routingKey, Object message, int millis) {
        log.info("发送延时消息:[{}],exchange:[{}],routingKey:[{}],延时时间为[{}]ms", message, exchange, routingKey, millis);
        rabbitTemplate.convertSendAndReceive(exchange, routingKey, message, msg -> {
            msg.getMessageProperties().setDelay(millis);
            return msg;
        });
    }

    private TaskStatus getTaskStatus(LocalDateTime startTime, LocalDateTime endTime) {
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(startTime)) {
            // now<startTime
            return TaskStatus.NOT_START;
        } else if (now.isBefore(endTime)) {
            // now>=startTime && now<endTime
            return TaskStatus.IN_PROGRESS;
        } else {
            // now>=endTime
            return TaskStatus.END;
        }
    }
}
